#include <OpenTissue/core/containers/mesh/trimesh/trimesh.h>
#include <OpenTissue/core/containers/mesh/common/util/mesh_make_plane.h>
#include <animation/cloud_shader.h>

template<typename iterator>
class dist_sort{
public:
	bool operator() (const iterator& lhs, const iterator& rhs) {
		return lhs->getDistanceToCamera()>rhs->getDistanceToCamera();
	}
};

template<typename math_types_ >
class CloudPuff{
public:
	typedef  math_types_  math_types;
	typedef typename math_types::vector3_type   vector3_type;
	typedef typename math_types::real_type   real_type;
private:
  vector3_type  m_center;
  real_type     m_transparency;
  real_type     m_distance;//distance from center of cloud to puff center.
  float   m_radius;
  bool    m_square;
  float m_distance_to_camera;
  typedef typename math_types::quaternion_type   quaternion_type;

  GLfloat m_u0;
  GLfloat m_u1;
  GLfloat m_v0;
  GLfloat m_v1;
public:
	void calcDistanceToCamera(vector3_type camera_pos){
		m_distance_to_camera=length(m_center-camera_pos);
	}
	float getDistanceToCamera(){
		return m_distance_to_camera;
	}

	CloudPuff(vector3_type center,float dist){
		m_center=center;
		m_distance=dist;

		m_transparency=0.0;
        
		m_radius=(float)(rand()%5+5.0);
		int tile=rand()%7;
		m_square=true;
		float   tileu;
		float   tilev;
		if(center(2)-m_radius<20.0){
			center(2)=20.0+m_radius;
		}
		switch(tile){
			case 0:
				tileu=0.75;
				tilev=0.75;
				m_square=false;
				center(2)=20.0+m_radius;
			break;
			case 1:
				tileu=0.50;
				tilev=0.25;
			break;
			case 2:
				tileu=0.75;
				tilev=0.25;
			break;
			case 3:
				tileu=0.0;
				tilev=0.0;
			break;
			case 4:
				tileu=0.25;
				tilev=0.0;
			break;
			case 5:
				tileu=0.5;
				tilev=0.0;
			break;
			case 6:
				tileu=0.75;
				tilev=0.0;
			break;
			default:
				std::cout<<tile<<std::endl;
		}
		m_u0=(GLfloat)tileu;
		m_u1=(GLfloat)tileu+0.25;
		m_v0=(GLfloat)tilev;
		m_v1=(GLfloat)tilev+0.25;
	}
	void setDistance(float dist){
		m_distance=dist;
	}

	float getDistance(){
		return m_distance;
	}
	void setTransparency(float t){
		m_transparency=t;
	}

	float getTransparency(){
		return m_transparency;
	}

	void setCenter(vector3_type center){
		m_center=center;
	}
	vector3_type getCenter(){
		return m_center;
	}
	bool getSquare(){
		return m_square;
	}
	void render(){	
		glBegin(GL_QUADS);
		glTexCoord2d(m_u0, m_v0);
		glColor3f(0, 0, (GLfloat) m_transparency);
		glVertex3f(-m_radius,0.0,-m_radius);

		glTexCoord2d(m_u1, m_v0);
		glColor3f(0, 0, (GLfloat) m_transparency);
		glVertex3f(m_radius,0.0,-m_radius);
		
		glTexCoord2d(m_u1, m_v1);
		glColor3f(0, 0, (GLfloat) m_transparency);
		glVertex3f(m_radius,0.0,m_radius);

		glTexCoord2d(m_u0, m_v1);
		glColor3f(0, 0, (GLfloat) m_transparency);
		glVertex3f(-m_radius,0.0,m_radius);
		glEnd();
	}
};


template<typename math_types_>
class Cloud{
public:
	typedef  math_types_  math_types;
	typedef typename math_types::real_type      real_type;
	typedef typename math_types::vector3_type   vector3_type;
	typedef typename math_types::quaternion_type   quaternion_type;
	typedef  CloudPuff<math_types>  puff_type;
	typedef  std::list<puff_type> puff_list_type;
	typedef typename puff_list_type::iterator puff_it;	
private:
    puff_list_type m_puffs;
	vector3_type m_center;
	int  m_state;//state:1 forming inner cloud 
	             //      2 forming outer cloud 
	             //      3 disapating outer cloud
	             //      4 disapating inner cloud
		         //      5 sets the grace period
		         //      6 substracts from  the grace period
	int  m_grace_period;//The number of update calls before start forming the cloud
	float m_distance_to_camera;
public:
	void calcDistanceToCamera(vector3_type camera_pos){
		m_distance_to_camera=length(m_center-camera_pos);
	}
	float getDistanceToCamera(){
		return m_distance_to_camera;
	}

	Cloud(vector3_type center){
        m_center=center;
		m_state=6;
		m_grace_period=rand()%200;
		int dx=rand()%40+5;
		int dy=rand()%40+5;
		int dz=rand()%10+2;
		float rx=(float)dx/2.0;
		float ry=(float)dy/2.0;
		float radius=sqrt(rx*rx+ry*ry);
		for(int j=0;j<rand()%200+100;j++){
            vector3_type position=vector3_type((float)(rand()%dx-rx),(float)(rand()%dy-ry),(float)(rand()%dz));
			float dist=sqrt(position(0)*position(0)+position(1)*position(1));
			puff_type temp=puff_type(position,dist/radius);
			m_puffs.push_back(temp);
		}
	}
	void setCenter(vector3_type center){
		m_center=center;
	}
	vector3_type getCenter(){
		return m_center;
	}
	void update(vector3_type wind,real_type time,real_type dx,real_type dy){
		m_center+=wind*time;
		if(m_center(0)<0.0){
			m_center(0)=dx-m_center(0);
		}else{
			if(m_center(0)>dx){
				m_center(0)-=dx;
			}
		}
		if(m_center(1)<0.0){
			m_center(1)=dy-m_center(1);
		}else{
			if(m_center(1)>dy){
				m_center(1)-=dy;
			}
		}
		bool changeState=true;
		switch(m_state){
			case 1:
				for(puff_it i=m_puffs.begin();i!=m_puffs.end();++i){
					if(i->getDistance()<0.5){
						float t=i->getTransparency();
						if(t<0.5){
							changeState=false;
						}					
						t+=0.02;
						i->setTransparency(t);
					}
				}
			break;
			case 2:
				for(puff_it i=m_puffs.begin();i!=m_puffs.end();++i){
					float t=i->getTransparency();
					if(t<0.75){	
						t+=0.02;
						i->setTransparency(t);
						changeState=false;
					}
				}
			break;
			case 3:
				for(puff_it i=m_puffs.begin();i!=m_puffs.end();++i){
					if(i->getDistance()>0.5){
						float t=i->getTransparency();
						if(t>0.0){
							changeState=false;
						}	
						t-=0.02;
						i->setTransparency(t);
					}
				}
			break;
			case 4:
				for(puff_it i=m_puffs.begin();i!=m_puffs.end();++i){
					float t=i->getTransparency();
					if(t>0.0){	
						t-=0.02;
						i->setTransparency(t);
						changeState=false;
					}
				}
			break;
			case 5:
				m_grace_period=rand()%200;
			break;
			case 6:
				m_grace_period--;
				if(m_grace_period<=0){
					m_state=0;
				}else{
					changeState=false;
				}
			break;

		}
		if(changeState){
			m_state++;
		}
	}    
	void render(vector3_type cam_pos,vector3_type cam_dir,vector3_type system_position,animation::CloudShader& shader){
		if(m_state>4)return;
		//the clouds location in wcs;
		vector3_type offset=m_center+system_position;

		//orders the sprites in the cloud by their distance to the camera.
		std::list<puff_it> order;
		for(puff_it i=m_puffs.begin();i!=m_puffs.end();++i){
			i->calcDistanceToCamera(cam_pos);
			if(i->getDistanceToCamera()<0.2){
				continue;
			}
			order.push_back(i);
		}
        order.sort(dist_sort<puff_it>());


		glPushMatrix();
		//translates to the clouds coordinate system
		OpenTissue::gl::Transform(m_center);	
		//the normal of the cloud
		vector3_type normal=vector3_type(0,1,0);
		vector3_type target_normal=cam_dir*-1;		
		for(std::list<puff_it>::iterator i=order.begin();i!=order.end();++i){
			if(m_state==1&&(*i)->getDistance()>0.5){
				continue;
			}
            //gets the sprites position in wcs.
			vector3_type position=(**i).getCenter()+offset;
			//gets the target normal towards the camera.
			vector3_type target_normal=cam_pos-position;
			//calculates the angle between the current and target normal.
			real_type angle=acos(dot(normal,target_normal)/length(target_normal));
			//calculates the amount of the angle into each of the 3 axis.
			vector3_type axis=normalize(cross(normal,target_normal));

			real_type s=sin(angle/2);
			//creates a quarternaion
			quaternion_type q=quaternion_type(cos(angle/2),0,axis(1)*s,axis(2)*s);
			/*
			if((*i)->getSquare()){
				q=quaternion_type(cos(angle/2),axis(0)*s,axis(1)*s,axis(2)*s);
			}else{
				q=quaternion_type(cos(angle/2),0,axis(1)*s,axis(2)*s);//axis(0)*s
			}*/

			glPushMatrix();
			//translates to the sprites coordinate system
			
			OpenTissue::gl::Transform((*i)->getCenter());
			OpenTissue::gl::Transform(q);
			//rotate the sprite with the quarternion;
			   
            shader.pre_render();
			(*i)->render();
			shader.post_render();
			glPopMatrix();
		}
	    glPopMatrix();
	}
};   


template<typename math_types_>
class Cloud_System{
public:
    typedef  math_types_               math_types;
	typedef  Cloud<math_types>         cloud_type;
	typedef typename math_types::real_type      real_type;
	typedef typename math_types::vector3_type   vector3_type;
	typedef std::list<cloud_type>      cloud_list_type;
	typedef typename cloud_list_type::iterator  cloud_list_type_it;
private:
	cloud_list_type m_clouds; 
	real_type       m_dx;
	real_type       m_dy;
	animation::CloudShader     m_shader;
public:
	Cloud_System(int dx,int dy){
		m_dy=(real_type)dy;
		m_dx=(real_type)dx;
		for(int i=0;i<25;i++){
			cloud_type cloud=cloud_type(vector3_type((float)(rand()%dx),(float)(rand()%dy),20));
			m_clouds.push_back(cloud);
		}
	}
	void update(vector3_type wind,real_type time){
		for(cloud_list_type_it i=m_clouds.begin();i!=m_clouds.end();++i){
			i->update(wind,time,m_dx,m_dy);
		}
	}
	void render(vector3_type cam_pos,vector3_type cam_dir,vector3_type system_position){
        m_shader.loadFragmentShader();
		std::list<cloud_list_type_it> order;
		for(cloud_list_type_it i=m_clouds.begin();i!=m_clouds.end();++i){
            i->calcDistanceToCamera(cam_pos);
			order.push_back(i);
		}
        order.sort(dist_sort<cloud_list_type_it>());
		
		glDepthMask(false);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		for(std::list<cloud_list_type_it>::iterator i=order.begin();i!=order.end();++i){
			(**i).render(cam_pos,cam_dir,system_position,m_shader);
		}
		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
		glDepthMask(true);
	}
};